﻿// The Nova Project by Ken Beckett.
// Copyright (C) 2007-2012 Inevitable Software, all rights reserved.
// Released under the Common Development and Distribution License, CDDL-1.0: http://opensource.org/licenses/cddl1.php

using System.Collections.Generic;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;

using Nova.CodeDOM;

namespace Nova.UI
{
    /// <summary>
    /// The view model for a <see cref="CodeDOM.Comment"/>.
    /// </summary>
    public class CommentVM : CommentBaseVM
    {
        #region /* STATICS */

        internal static void AddViewModelMapping()
        {
            CreateViewModel.Add(typeof(Comment),
                delegate(CodeObject codeObject, bool isDescription, Dictionary<CodeObject, CodeObjectVM> dictionary) { return new CommentVM((Comment)codeObject, dictionary); });
        }

        #endregion

        #region /* CONSTRUCTORS */

        /// <summary>
        /// Create a view model instance for the specified <see cref="CodeDOM.Comment"/>.
        /// </summary>
        public CommentVM(Comment comment, Dictionary<CodeObject, CodeObjectVM> dictionary)
            : base(comment, dictionary)
        { }

        #endregion

        #region /* PROPERTIES */

        /// <summary>
        /// The underlying <see cref="CodeDOM.Comment"/> model.
        /// </summary>
        public Comment Comment
        {
            get { return (Comment)CodeObject; }
        }

        #endregion

        #region /* METHODS */

        #endregion

        #region /* RENDERING */

        /// <summary>
        /// Determines if comment text is rendered with a proportional font instead of a fixed font.
        /// </summary>
        public static bool UseProportionalFont;

        public static readonly Brush StaticBorderBrush = Brushes.LightGray;
        public static readonly Brush StaticBackgroundBrush = Brushes.White;

        public override Brush BorderBrush
        {
            get { return StaticBorderBrush; }
        }

        public override Brush BackgroundBrush
        {
            get { return StaticBackgroundBrush; }
        }

        public override int BorderHPad
        {
            get { return 2; }
        }

        public override int BorderVPad
        {
            get { return 1; }
        }

        public override void Render(CodeRenderer renderer, RenderFlags flags)
        {
            if (HideAll) return;

            Comment comment = Comment;
            bool isEOL = comment.IsEOL;
            bool isInline = flags.HasFlag(RenderFlags.CommentsInline);
            bool isBlock = (comment.IsBlock || isInline);
            bool isRawFormat = comment.IsRawFormat;

            int newLines = comment.NewLines;
            bool isPrefix = flags.HasFlag(RenderFlags.IsPrefix);
            if (!isPrefix)
            {
                if (newLines > 0)
                {
                    if (!flags.HasFlag(RenderFlags.SuppressNewLine))
                        renderer.NewLines(newLines, ParentVM);
                }
                else if (!comment.IsInfix || isEOL)
                    renderer.RenderText(isBlock ? " " : "  ", PUNC_BRUSH, ParentVM is BlockVM ? ParentVM.ParentVM : ParentVM);
            }

            CreateBorder(renderer, flags);

            TextStyle textStyle = (UseProportionalFont ? TextStyle.Proportional : TextStyle.Normal);
            string space = (comment.NoSpaceAfterDelimiter ? "" : " ");
            string text = comment.Text;
            if (text != null)
            {
                if (comment.IsTODO || comment.IsHack)
                {
                    // Render special comments such that we can use a different color for the text portion
                    if (isEOL || isInline)
                    {
                        // Render a single-line EOL or in-line block comment.
                        // There shouldn't be any newlines in the comment, but handle them just in case.
                        if (!HideDelimiters)
                            renderer.RenderText((isBlock ? Comment.ParseTokenBlockStart : Comment.ParseToken) + space, COMMENT_BRUSH, this);
                        renderer.RenderText(text.Replace("\n", "; "), COMMENT_SPECIAL_BRUSH, TextStyle.Bold | TextStyle.NoCache, this);
                        if (isBlock && !HideDelimiters)
                            renderer.RenderText((text.EndsWith("*") ? "" : space) + Comment.ParseTokenBlockEnd, COMMENT_BRUSH, this);
                    }
                    else
                    {
                        // Render a multi-line comment or block comment
                        string[] lines = text.Split('\n');
                        bool lastIsEmpty = true;
                        for (int i = 0; i < lines.Length; ++i)
                        {
                            string line = lines[i];
                            lastIsEmpty = (isBlock && (i == lines.Length - 1) && string.IsNullOrEmpty(line));
                            if (i > 0 && !(lastIsEmpty && HideDelimiters))
                                renderer.NewLine();
                            if (!HideDelimiters)
                            {
                                renderer.RenderText(isBlock ? (i == 0 ? Comment.ParseTokenBlockStart + space : (isRawFormat ? "" : (lastIsEmpty ? " " : " *" + space)))
                                    : Comment.ParseToken + space, COMMENT_BRUSH, this);
                            }
                            if (lastIsEmpty)
                                break;
                            renderer.RenderText(line, COMMENT_SPECIAL_BRUSH, textStyle | TextStyle.Bold | TextStyle.NoCache, this);
                        }
                        if (isBlock && !HideDelimiters)
                            renderer.RenderText(((isRawFormat || lastIsEmpty || text.EndsWith("*")) ? "" : space) + Comment.ParseTokenBlockEnd, COMMENT_BRUSH, this);
                    }
                }
                else
                {
                    // Render regular comments as a single string for efficiency
                    string commentText;
                    if (isEOL || isInline)
                    {
                        // Render a single-line EOL or in-line block comment.
                        // There shouldn't be any newlines in the comment, but handle them just in case.
                        commentText = (HideDelimiters ? "" : (isBlock ? Comment.ParseTokenBlockStart : Comment.ParseToken) + space)
                            + text.Replace("\n", "; ") + ((isBlock && !HideDelimiters) ? ((text.EndsWith("*") ? "" : space) + Comment.ParseTokenBlockEnd) : "");
                    }
                    else
                    {
                        // Render a multi-line comment or block comment
                        commentText = null;
                        string[] lines = text.Split('\n');
                        bool lastIsEmpty = true;
                        for (int i = 0; i < lines.Length; ++i)
                        {
                            string line = lines[i];
                            lastIsEmpty = (isBlock && (i == lines.Length - 1) && string.IsNullOrEmpty(line));
                            if (i > 0 && !(lastIsEmpty && HideDelimiters))
                                commentText += '\n';
                            if (!HideDelimiters)
                                commentText += (isBlock ? (i == 0 ? Comment.ParseTokenBlockStart + space : (isRawFormat ? "" : (lastIsEmpty ? " " : " *" + space))) : Comment.ParseToken + space);
                            commentText += line;
                        }
                        if (isBlock && !HideDelimiters)
                            commentText += ((isRawFormat || lastIsEmpty || text.EndsWith("*")) ? "" : space) + Comment.ParseTokenBlockEnd;
                    }
                    renderer.RenderText(commentText, COMMENT_BRUSH, textStyle | TextStyle.NoCache, this);
                }
            }

            renderer.ReturnToBorderParent(this);

            if (isPrefix)
            {
                // If this object is rendered as a child prefix object of another, then any whitespace is
                // rendered here *after* the object instead of before it.
                if (newLines > 0)
                    renderer.NewLines(newLines, ParentVM);
                else
                    renderer.RenderText(" ", PUNC_BRUSH, ParentVM);
            }
        }

        public override void RenderToolTip(CodeRenderer renderer, RenderFlags flags)
        {
            TypeRefBaseVM.RenderType(renderer, CodeObject.GetType(), flags, this);
            renderer.RenderText(" :  ", NORMAL_BRUSH, TextStyle.Proportional | TextStyle.Bold);
            string flagsString = Comment.FlagsAsText();
            if (flagsString != "None")
                renderer.RenderText(flagsString + ", ", NORMAL_BRUSH, TextStyle.Proportional);
            renderer.RenderText("Parent = ", NORMAL_BRUSH, TextStyle.Proportional);
            ParentVM.Render(renderer, RenderFlags.Description | RenderFlags.SuppressNewLine | RenderFlags.ForceBorder);
            RenderMessagesInToolTip(renderer, flags);
        }

        #endregion

        #region /* COMMANDS */

        public static readonly RoutedCommand UseProportionalFontCommand = new RoutedCommand("Use Proportional Font for Comments", typeof(CommentVM));

        public static new void BindCommands(Window window)
        {
            WPFUtil.AddCommandBinding(window, UseProportionalFontCommand, proportionalComment_Executed);
        }

        private static void proportionalComment_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            UseProportionalFont = !UseProportionalFont;
            CodeRenderer.ReRenderAll();
        }

        #endregion
    }
}
